package com.jrelax.web.codegen.controller;

import com.jrelax.core.support.ApplicationCommon;
import com.jrelax.core.web.converter.WebParam;
import com.jrelax.core.web.support.WebResult;
import com.jrelax.core.web.support.http.ContentType;
import com.jrelax.kit.FileKit;
import com.jrelax.kit.StringKit;
import com.jrelax.kit.ZipKit;
import com.jrelax.web.support.BaseController;
import com.jrelax.web.system.service.DataBaseService;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.sql.DataSource;
import java.io.*;
import java.math.BigDecimal;
import java.util.*;

@Controller
@RequestMapping("/open/dev/code")
public class CodeController extends BaseController<Object> {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final String TPL = "/open/dev/code/";
    private static Map<String, JSONObject> mapStatus = new HashMap<>();

    @Autowired
    private DataBaseService dbService;

    private final String ENTITY_PATH = "/src/main/java/";
    private final String TPL_PATH = "/src/main/resources/templates/";

    @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
    public String index(Model model) {
        logger.debug("访问代码生成工具 - 首页");
        List<String> catalogs = dbService.getAllCatalog();
        model.addAttribute("cur", dbService.getCatalog());
        model.addAttribute("list", catalogs);
        return TPL + "step1";
    }

    @RequestMapping(value = "/step/2", method = {RequestMethod.GET, RequestMethod.POST})
    public String step2(Model model, String catalog) {
        List<String> tableNames = dbService.getTables(catalog, null);
        model.addAttribute("list", tableNames);
        model.addAttribute("catalog", catalog);
        return TPL + "step2";
    }

    @RequestMapping(value = "/step/3", method = {RequestMethod.GET, RequestMethod.POST})
    public String step3(Model model, String catalog, String[] tables) {
        model.addAttribute("tables", StringKit.toString(tables));
        //按照选择的表，自动生成表前缀，和包名
        String prefix = "";
        String pack = "";
        for (String t : tables) {
            if (prefix.length() == 0) {
                prefix = t.substring(0, t.indexOf("_") + 1);
            } else {
                if (!t.startsWith(prefix))
                    prefix = "";
            }
        }
        pack = prefix.replaceAll("_", "");
        //获取表字段
        Map<String, Map<String, String>> tableColumns = new LinkedHashMap<>();
        for (String table : tables) {
            tableColumns.put(table, dbService.getColumnRemarks(catalog, null, table));
        }
        model.addAttribute("tableColumns", tableColumns);
        model.addAttribute("prefix", prefix);
        model.addAttribute("pack", pack);
        model.addAttribute("catalog", catalog);
        String platform = System.getProperty("os.name");
        if (platform.contains("Windows"))
            model.addAttribute("savePath", "C:/output");
        else
            model.addAttribute("savePath", System.getProperty("user.home") + "/output");
        return TPL + "step3";
    }

    @RequestMapping(value = "/step/4", method = {RequestMethod.GET, RequestMethod.POST})
    public String step4(Model model, final String columnMapping, final String catalog, final String tables, final String tablePrefix, final String packagePrefix, final String packageName, final String savePath, final boolean entityKeepPrefix) {
        final String p_controller = packagePrefix + "." + packageName + ".controller";
        final String p_entity = packagePrefix + "." + packageName + ".entity";
        final String p_service = packagePrefix + "." + packageName + ".service";
        final String tableFirstName = StringKit.firstUpperCase(tablePrefix.replace("_", ""));
        final JSONArray mappings = JSONArray.fromObject(columnMapping);
        setSessionAttr("saveDir", savePath);

        final String sessionId = getSession().getId();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                double process = 5;
                setStatus(sessionId, process, "正在生成...");
                /**************************清理输出目录***************************/
                try {
                    FileKit.deleteDirectory(new File(savePath));
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //0. 初始化Velocity + 数据库表
                Properties prop = new Properties();
                String loadPath = ApplicationCommon.WEBAPPS_PATH + "/codegen/template";
                prop.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, loadPath);
                VelocityEngine ve = new VelocityEngine(prop);
                ve.init();

                String[] tableArray = tables.split(",");
                for (String table : tableArray) {
                    /**************************初始化参数***************************/
                    JSONObject mapping = new JSONObject();
                    for (int i = 0; i < mappings.size(); i++) {
                        JSONObject obj = mappings.getJSONObject(i);
                        if (table.equals(obj.getString("table"))) {
                            mapping = obj.getJSONObject("columns");
                            break;
                        }
                    }
                    VelocityContext context = new VelocityContext();
                    Map<String, Map<String, Object>> columns = dbService.getColumnFull(catalog, null, table);
                    for (Object k : mapping.keySet()) {
                        String key = k.toString();
                        if (columns.containsKey(k)) {
                            columns.get(k).put("displayName", mapping.get(key));
                        }
                    }
                    Map<String, String> forgetColumns = dbService.getForgetKeys(catalog, null, table);
                    //获取外键关联
                    Map<String, List<String>> _forgetColumns = new HashMap<>();
                    for (String key : forgetColumns.keySet()) {
                        List<String> infos = new ArrayList<String>();
                        String tableName = forgetColumns.get(key);//表名
                        String className = entityKeepPrefix ? this.getClassName(tableName) : this.getSimpleClassName(tableName);//类名
                        String vName = StringKit.firstLowerCase(className.replace(tableFirstName, ""));//类变量名

                        infos.add(className);
                        infos.add(vName);
                        infos.add(StringKit.firstUpperCase(vName));

                        _forgetColumns.put(key, infos);
                    }
                    context.put("layout", "$!layout");
                    context.put("basePath", "$!basePath");
                    context.put("basepack", packagePrefix);//包根目录
                    context.put("columns", columns);//列
                    context.put("forgetColumns", _forgetColumns);//外键
                    context.put("tableName", table);//表名
                    String className = entityKeepPrefix ? this.getClassName(table) : this.getSimpleClassName(table);
                    String classNameLower = StringKit.firstLowerCase(className);

                    context.put("className", className);//类名
                    context.put("classNameLower", classNameLower);//类名首字母小写

                    /**************************生成类***************************/
                    //1. 生成实体类
                    context.put("pack", p_entity);
                    context.put("entity_pack", p_entity);
                    context.put("hasBigDecimal", false);
                    for (String col : columns.keySet()) {
                        Map<String, Object> map = columns.get(col);
                        if (map.get("type").equals("java.math.BigDecimal")) {
                            context.put("hasBigDecimal", true);
                            map.put("type", "BigDecimal");
                            columns.put(col, map);
                        }
                    }
                    //获取外键
                    Template template = ve.getTemplate("Entity.j", "utf-8");
                    String dir = savePath + ENTITY_PATH + p_entity.replace(".", "/") + "/";
                    File fDir = new File(dir);
                    if (!fDir.exists())
                        fDir.mkdirs();
                    String path = dir + className + ".java";
                    File entity = new File(path);
                    mergeTemplate(context, template, entity, true);
                    codeFormater(entity);

                    //2. 生成Service类
                    context.put("pack", p_service);
                    context.put("service_pack", p_service);
                    template = ve.getTemplate("Service.j", "utf-8");
                    dir = savePath + ENTITY_PATH + p_service.replace(".", "/") + "/";
                    fDir = new File(dir);
                    if (!fDir.exists())
                        fDir.mkdirs();
                    path = dir + className + "Service.java";
                    File service = new File(path);
                    mergeTemplate(context, template, service, true);
                    codeFormater(service);

                    //3. 生成Controller类
                    context.put("pack", p_controller);
                    context.put("requestMapping", "/" + table.replace("_", "/"));
                    template = ve.getTemplate("Controller.j", "utf-8");
                    dir = savePath + ENTITY_PATH + p_controller.replace(".", "/") + "/";
                    fDir = new File(dir);
                    if (!fDir.exists())
                        fDir.mkdirs();
                    path = dir + className + "Controller.java";
                    File controller = new File(path);
                    mergeTemplate(context, template, controller, true);
//                    codeFormater(controller);

                    /**************************生成页面***************************/
                    String simpleClassNameLower = this.getSimpleClassName(table);//弃用
                    simpleClassNameLower = table.replace(packageName + "_", "").replaceAll("_", "/");
                    //4. 生成index页面
                    context.put("action", "$!basePath/" + packageName + "/" + simpleClassNameLower);
                    template = ve.getTemplate("page/index.html", "utf-8");
                    dir = savePath + TPL_PATH + packageName + "/" + simpleClassNameLower + "/";
                    fDir = new File(dir);
                    if (!fDir.exists())
                        fDir.mkdirs();
                    path = dir + "index.html";
                    File index = new File(path);
                    mergeTemplate(context, template, index, true);
                    //5. 生成add页面
                    template = ve.getTemplate("page/add.html", "utf-8");
                    dir = savePath + TPL_PATH + packageName + "/" + simpleClassNameLower + "/";
                    fDir = new File(dir);
                    if (!fDir.exists())
                        fDir.mkdirs();
                    path = dir + "add.html";
                    File add = new File(path);
                    mergeTemplate(context, template, add, true);
                    //6. 生成edit页面
                    template = ve.getTemplate("page/edit.html", "utf-8");
                    dir = savePath + TPL_PATH + packageName + "/" + simpleClassNameLower + "/";
                    fDir = new File(dir);
                    if (!fDir.exists())
                        fDir.mkdirs();
                    path = dir + "edit.html";
                    File edit = new File(path);
                    mergeTemplate(context, template, edit, true);
                    //7. 生成detail页面
                    template = ve.getTemplate("page/detail.html", "utf-8");
                    dir = savePath + TPL_PATH + packageName + "/" + simpleClassNameLower + "/";
                    fDir = new File(dir);
                    if (!fDir.exists())
                        fDir.mkdirs();
                    path = dir + "detail.html";
                    File detail = new File(path);
                    mergeTemplate(context, template, detail, true);

                    process += ((1.0 / tableArray.length) * 100);
                    setStatus(sessionId, process, "正在生成" + table + "相关模块...");
                }
                setStatus(sessionId, 100, "生成完成");
            }

            /**
             * 生成类名
             * @param tableName
             * @return
             */
            public String getSimpleClassName(String tableName) {
                String _name = tableName.replace(tablePrefix, "");
                String[] array = _name.split("_");
                String className = "";
                for (String n : array) {
                    className += StringKit.firstUpperCase(n);
                }
                return className;
            }

            /**
             * 生成类名
             * @param tableName
             * @return
             */
            public String getClassName(String tableName) {
                String _name = tableName.replace(tablePrefix, "");
                String[] array = _name.split("_");
                String className = "";
                for (String n : array) {
                    className += StringKit.firstUpperCase(n);
                }
                className = tableFirstName + className;
                return className;
            }

            /**
             * Java代码格式化
             * @param entity
             */
            private void codeFormater(File entity) {
                //代码格式化
//                try {
//                    Jalopy ja = new Jalopy();
//                    ja.setInput(entity);
//                    ja.setOutput(entity);
//                    ja.setEncoding("UTF-8");
//                    ja.format();
//                } catch (IOException e1) {
//                    e1.printStackTrace();
//                }
            }

            /**
             * Velocity 生成类和页面
             * @param context
             * @param template
             * @param add
             */
            private void mergeTemplate(VelocityContext context, Template template, File add, boolean encoding) {
                try {
                    FileOutputStream fos = new FileOutputStream(add);
                    OutputStreamWriter osw;
                    if (!encoding)
                        osw = new OutputStreamWriter(fos);
                    else
                        osw = new OutputStreamWriter(fos, "utf-8");
                    BufferedWriter bw = new BufferedWriter(osw);
                    template.merge(context, bw);
                    bw.close();
                    osw.close();
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

        model.addAttribute("savePath", savePath);
        return TPL + "step4";
    }

    /**
     * 代码生成进度
     *
     * @return
     */
    @RequestMapping(value = "/status")
    @ResponseBody
    public JSONObject status() {
        return mapStatus.get(getSession().getId());
    }

    /**
     * 设置状态
     *
     * @param process
     * @param msg
     */
    public void setStatus(String sessionId, double process, String msg) {
        if (process > 100) process = 100;
        JSONObject jsonProcess = new JSONObject();
        jsonProcess.element("process", new BigDecimal(process).intValue());
        jsonProcess.element("msg", msg);
        mapStatus.put(sessionId, jsonProcess);
    }

    /**
     * 打开本地目录
     *
     * @param path
     * @return
     */
    @RequestMapping(value = "/open/folder", method = {RequestMethod.POST})
    @ResponseBody
    public JSONObject openFolder(String path) {
        File dir = new File(path);
        if (dir.exists())
            path = dir.getAbsolutePath();
        else return WebResult.error("文件夹不存在！");
        try {
            if (getRequestAddr().equals("本地主机")) {
                String platform = System.getProperty("os.name");
                if (platform.contains("Windows")) {
                    try {
                        String[] cmd = new String[5];
                        cmd[0] = "cmd";
                        cmd[1] = "/c";
                        cmd[2] = "start";
                        cmd[3] = " ";
                        cmd[4] = path;
                        Runtime.getRuntime().exec(cmd);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else if (platform.contains("Mac")) {
                    try {
                        String[] cmd = new String[3];
                        cmd[0] = "open";
                        cmd[1] = "-n";
                        cmd[2] = path;
                        Runtime.getRuntime().exec(cmd);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    return WebResult.error("当前系统不支持打开目录");
                }
                return WebResult.success();
            } else {
                return WebResult.error("只能打开本地目录！");
            }
        } catch (Exception e) {
            logger.error(e.toString());
            return WebResult.error(e);
        }
    }

    /**
     * 下载文件
     */
    @RequestMapping("/download")
    public void download() {
        //先打包为zip
        //再下载
        WebParam param = getSessionAttr("saveDir");
        if (!param.isNull()) {
            String savePath = param.stringValue();
            String zipFile = savePath + "/code.zip";
            File file = new File(zipFile);
            if (file.exists()) {
                if (!file.delete()) {
                    return;
                }
            }
            ZipKit.compressDirectory(savePath, zipFile);
            renderFile(ContentType.ZIP, file);
        }
    }
}
